<?php
namespace yan\xss_sql_filter;

class XssAndSqlFilter{
    private static $_instances = [];
    private static $key = "eval|and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|=|if|(|)|while";
    private static $notAllowedKeyWords = [];
    public function __construct()
    {
        self::$notAllowedKeyWords = explode('|', self::$key);
    }
    public static function instance($refresh = false){
        $className = get_called_class();
        if($refresh || empty(self::$_instances[$className])){            
            self::$_instances[$className] = new $className;
        }
        return self::$_instances[$className];
    }
    public static function getParameterValues($params){
        if(is_array($params)){
            foreach($params as &$val){
                if(is_array($val)){
                    $val = self::getParameterValues($val);
                }else{
                    $val = self::xssEncode($val);
                }
            }
        }else{
            $params = self::xssEncode($params);
        }
        return $params;
    }
    /**
     * 将容易引起xss &amp; sql漏洞的半角字符直接替换成全角字符
     *
     * @param s
     * @return
     */
    public static function xssEncode(String $s) {
        if (empty($s)) {
            return $s;
        } else {
            $s = self::stripXSSAndSql($s);
        }
        $sb = [];
        for ($i = 0; $i < strlen($s); $i++) {
            $c = $s[$i];
            switch ($c) {
                case '\'':
                    $sb[$i] = "＇";// 转义单引号
                    break;
                case '"':
                    $sb[$i] = "＂";// 转义双引号
                    break;
                case '\\':
                    $sb[$i] = '＼';//全角斜线
                    break;
                case '>':
                    $sb[$i] = "＞";// 转义>
                case '<':
                    $sb[$i] = "＜";// 转义<
                case '&':
                    $sb[$i] = "＆";// 转义&
                    break;
                case '#':
                    $sb[$i] = "＃";// 转义#
                    break;
                default:
                    $sb[$i] = $c;
                    break;
            }
        }
        return implode('', $sb);
    }
    public static function generate_regex_for_word($word) {
        // 转义特殊字符
        // $safe_word = preg_quote($word);
        // 构建正则表达式
        // $regex = "/\b{$safe_word}\b/is";
        $regex = "/".$word."/i";
        return $regex;
    }
    /**
     *
     * 防止xss跨脚本攻击（替换，根据实际情况调整）
     */
    public static function stripXSSAndSql(String $value) {
        if ( !empty($value) ) {
            // NOTE: It's highly recommended to use the ESAPI library and
            // uncomment the following line to
            // avoid encoded attacks.
            // value = ESAPI.encoder().canonicalize(value);
            // Avoid null characters
            /** value = value.replaceAll("", ""); ***/
            // Avoid anything between script tags
            $scriptPattern = self::generate_regex_for_word("<[\r|\n|\s| ]*script[^>]*>(.*?)<\/[\r|\n|\s| ]script[^>]*>");
            $value =  preg_replace($scriptPattern, "", $value);
            // Avoid anything in a
            // src="http://www.yihaomen.com/article/java/..." type of
            // e-xpression
            $scriptPattern = self::generate_regex_for_word("src[\r\n|\s| ]*=[\r\n|\s| ]*[\\\"|\\\'](.*?)[\\\"|\\\']");
            $value =  preg_replace($scriptPattern, "", $value);
            // Remove any lonesome  tag
            $scriptPattern = self::generate_regex_for_word("<!--[\r\n|\s| ]*script[\r\n|\s| ]*-->");
            $value =  preg_replace($scriptPattern, "", $value);
            // Remove any lonesome <script ...> tag
            $scriptPattern = self::generate_regex_for_word("<[\r\n|\s| ]*script(.*?)>");
            $value =  preg_replace($scriptPattern, "", $value);
            // Avoid eval(...) expressions
            $scriptPattern = self::generate_regex_for_word("eval\\((.*?)\\)");
            $value =  preg_replace($scriptPattern, "", $value);
            // Avoid e-xpression(...) expressions
            $scriptPattern = self::generate_regex_for_word("e-xpression\\((.*?)\\)");
            $value =  preg_replace($scriptPattern, "", $value);
            // Avoid javascript:... expressions
            $scriptPattern = self::generate_regex_for_word("javascript[\r\n|\s| ]*:[\r\n|\s| ]*");
            $value =  preg_replace($scriptPattern, "", $value);
            // Avoid vbscript:... expressions
            $scriptPattern = self::generate_regex_for_word("vbscript[\r\n|\s| ]*:[\r\n|\s| ]*");
            $value =  preg_replace($scriptPattern, "", $value);
            // Avoid onload= expressions
            $scriptPattern = self::generate_regex_for_word("onload(.*?)=");
            $value =  preg_replace($scriptPattern, "", $value);
        }
        return $value;
    }

    /**
     * return false is safe
     */
    public static function checkXSSAndSql(String $value) {
        $flag = false;
        if (!empty($value)) {
            // NOTE: It's highly recommended to use the ESAPI library and
            // uncomment the following line to
            // avoid encoded attacks.
            // value = ESAPI.encoder().canonicalize(value);
            // Avoid null characters
            /** value = value.replaceAll("", ""); ***/
            // Avoid anything between script tags
            $scriptPattern = self::generate_regex_for_word("<[\r|\n|\s| ]*script[^>]*>(.*?)<\/[\r|\n|\s| ]script[^>]*>");

            $flag = preg_match($scriptPattern, $value);
            if ($flag) {
                return $flag;
            }
            // Avoid anything in a
            // src="http://www.yihaomen.com/article/java/..." type of
            // e-xpression
            $scriptPattern = self::generate_regex_for_word("src[\r\n|\s| ]*=[\r\n|\s| ]*[\\\"|\\\'](.*?)[\\\"|\\\']");
            $flag = preg_match($scriptPattern, $value);
            if ($flag) {
                return $flag;
            }
            // Remove any lonesome </script> tag
            $scriptPattern = self::generate_regex_for_word("<!--[\r\n|\s| ]*script[\r\n|\s| ]*-->");
            $flag = preg_match($scriptPattern, $value);
            if ($flag) {
                return $flag;
            }
            // Remove any lonesome <script ...> tag
            $scriptPattern = self::generate_regex_for_word("<[\r\n|\s| ]*script(.*?)>");
            $flag = preg_match($scriptPattern, $value);
            if ($flag) {
                return $flag;
            }
            // Avoid eval(...) expressions
            $scriptPattern = self::generate_regex_for_word("eval\\((.*?)\\)");
            $flag = preg_match($scriptPattern, $value);
            if ($flag) {
                return $flag;
            }
            // Avoid e-xpression(...) expressions
            $scriptPattern = self::generate_regex_for_word("e-xpression\\((.*?)\\)");
            $flag = preg_match($scriptPattern, $value);
            if ($flag) {
                return $flag;
            }
            // Avoid javascript:... expressions
            $scriptPattern = self::generate_regex_for_word("javascript[\r\n|\s| ]*:[\r\n|\s| ]*");
            $flag = preg_match($scriptPattern, $value);
            if ($flag) {
                return $flag;
            }
            // Avoid vbscript:... expressions
            $scriptPattern = self::generate_regex_for_word("vbscript[\r\n|\s| ]*:[\r\n|\s| ]*");
            $flag = preg_match($scriptPattern, $value);
            if ($flag) {
                return $flag;
            }
            // Avoid onload= expressions
            $scriptPattern = self::generate_regex_for_word("onload(.*?)=");
            $flag = preg_match($scriptPattern, $value);
            if ($flag) {
                return $flag;
            }

            $flag = self::checkSqlKeyWords($value);
        }
        return $flag;
    }
    /**
     * return false keywords not have inject
     */
    public static function checkSqlKeyWords(String $value){
        $paramValue = $value;
        foreach (self::$notAllowedKeyWords as $keyword) {
            if (strlen($paramValue) > (strlen($keyword))
                    && (str_contains($paramValue, " ".$keyword)||str_contains($paramValue, " ".$keyword." "))) {                
                return true;
            }
        }
        return false;
    }

    public static function urlDecodeCheck(String $value){
        $value = urldecode($value);
        return self::checkXSSAndSql($value);
    }
    public static function htmlDecodeCheck(String $value){
        $value = html_entity_decode($value, ENT_COMPAT, 'UTF-8');
        return self::checkXSSAndSql($value);
    }
    public static function doCheck(String $value){
        $flag = self::checkXSSAndSql($value);
        if($flag){
            return $flag;
        }
        $flag = self::htmlDecodeCheck($value);
        if($flag){
            return $flag;
        }
        $flag = self::urlDecodeCheck($value);
        if($flag){
            return $flag;
        }
        return false;
    }
    public static function checkSpecialCharacters(String $value){
        $pattern = "/[\<|\>|\\|'|\"]/";
        if(preg_match($pattern, $value)){
            return true;
        }
        $value2 = urldecode($value);
        if(preg_match($pattern, $value2)){
            return true;
        }
        $value2 = html_entity_decode($value, ENT_COMPAT, 'UTF-8');
        if(preg_match($pattern, $value2)){
            return true;
        }
        return false;
    }
}

if (!function_exists('str_contains')) {
    function str_contains($haystack, $needle) {
        return $needle !== '' && mb_strpos($haystack, $needle) !== false;
    }
}